<?php 
// File -> app/controllers/components/remote_parser.php

/** 
 * RemoteParser Component
 * 
 * @author sky_l3ppard
 * @version 1.0
 * @license MIT
 * @category Components
 */

define('RP_DEBUG_LEVEL_NONE', 0);
define('RP_DEBUG_LEVEL_ERRORS', 1);
define('RP_DEBUG_LEVEL_FULL', 2);

class RemoteParserComponent extends Object {
    /**
     * Reference to controller
     * 
     * @var Object
     * @access Private
     */
    var $__controller = null;
    /**
     * Content from remote location
     * 
     * @var String
     * @access Private
     */
    var $__content = null;
    /**
     * Offset in remote content
     * 
     * @var Integer
     * @access Public
     */
    var $offset = null;
    /**
     * Length of remote content in bytes
     * 
     * @var Integer
     * @access Public
     */
    var $length = null;
    /**
     * Component debug level:
     *         0 - no debug output at all
     *         1 - logging only errors
     *         2 - full debug 
     * 
     * @var Integer
     * @access Public
     */
    var $debug_level = RP_DEBUG_LEVEL_FULL;
    /**
     * Resource handle returned by "curl_init" function more information can be
     * found at "http://www.php.net/manual/en/function.curl-init.php"
     * 
     * @var Resource
     * @access Private
     */
    var $__connection = null;
    /**
     * Result of the last executed operation, more information about
     * this resultset can be found at "http://www.php.net/manual/en/function.curl-getinfo.php"
     * 
     * @var Array 
     * @access Private
     */
    var $__connection_execution_info = null;
    /**
     * True to keep the CURL connection openned, required then web site is
     * using session for remote browsing
     * 
     * @var Boolean 
     * @access Private
     */
    var $__connection_keep_open = false;
    /**
     * User agent to show then connected to remote site
     * 
     * @var String
     * @access Public
     */
    var $user_agent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3';
    /**
     * Number of seconds then connection times out
     * 
     * @var Integer
     * @access Public
     */
    var $connection_timeout = 30;
    /**
     * True to fallow redirects then connecting to remote site
     * 
     * @var Boolean
     * @access Public
     */
    var $redirects_fallow = true;
    /**
     * Number of maximum redirects on remote site
     * 
     * @var integer
     * @access public
     */
    var $redirects_max = 5;
    
    
    /**
     * Initializes this component
     * 
     * @param Object $controller - reference to controller
     * @access Public
     */
    function initialize(&$controller) {
        $this->__controller = &$controller;
    }
    
    /**
     * Initializes Session for a CUrl connection
     * 
     * @access Private
     */
    function __initializeSession() {
        if (empty($this->__connection)) {
            $this->__initializeConnection();
        }
        
        $this->debug_log("Using session for open connection");
        curl_setopt($this->__connection, CURLOPT_COOKIESESSION, true);
        curl_setopt($this->__connection, CURLOPT_COOKIEFILE, "remote_connection_parser");
        curl_setopt($this->__connection, CURLOPT_COOKIEJAR, "remote_connection_parser");
        curl_setopt($this->__connection, CURLOPT_COOKIE, session_name() . '=' . session_id());
    }
    
    /**
     * Initializes CUrl connection handle
     * 
     * @access Private
     */
    function __initializeConnection() {
        $this->debug_log("Initializing CURL connection");
        $this->__connection = curl_init();
        
        curl_setopt($this->__connection, CURLOPT_CONNECTTIMEOUT, $this->connection_timeout);
        curl_setopt($this->__connection, CURLOPT_USERAGENT, $this->user_agent);
        curl_setopt($this->__connection, CURLOPT_FOLLOWLOCATION, $this->redirects_fallow);
        curl_setopt($this->__connection, CURLOPT_MAXREDIRS, $this->redirects_max);
    }

    /**
     * Logs component operations
     * 
     * @param String $msg - message to log
     * @param Integer $level - level of debug:
     *         0 - no debug output at all
     *         1 - logging only errors
     *         2 - full debug 
     * @access Public
     */
    function debug_log($msg, $level = RP_DEBUG_LEVEL_FULL) {
        if ($this->debug_level == RP_DEBUG_LEVEL_NONE) {
            return;
        }

        if ($this->debug_level == RP_DEBUG_LEVEL_ERRORS && $level != RP_DEBUG_LEVEL_ERRORS) {
            return;
        }
        
        if ($this->debug_level == RP_DEBUG_LEVEL_ERRORS) {
            $this->log($msg);
        }
        else {
            $this->log($msg, LOG_DEBUG);
        }
    }
    
    /**
     * Sets CUrl connection to be open, and initializes session
     * 
     * @access Public
     */
    function setOpenConnection() {
        $this->__connection_keep_open = true;
        $this->__initializeSession();
    }
    
    /**
     * Adds option to the CUrl connection handle, do not forget to reset it
     * if using diferent method later. More information can be found at
     * "http://www.php.net/manual/en/function.curl-setopt.php"
     * 
     * @param Integer $code - CUrl option code
     * @param Mixed $value - CUrl option value, type depends on option
     * @access Public 
     */
    function setOption($code, $value) {
        if (!$this->__connection_keep_open) {
            return;
        }
        
        if (empty($this->__connection)) {
            return;
        }
        $this->debug_log("Adding option code[$code], value[$value]");
        curl_setopt($this->__connection, $code, $value);
    }
    /**
     * Called before execution of Remote operation, by default
     * initializes CUrl connection it not set to open
     * 
     * @param String $url - reference to the passed url
     * @access Private
     */
    function __beforeExecution(&$url) {
        $url = str_replace(' ', '%20', $url);
        if ($this->__connection_keep_open) {
            return;
        }
        $this->__initializeConnection();
    }
    
    /**
     * Called after execution of Remote operation, by default
     * closes CUrl connection it not set to open
     * 
     * @param Integer $result - reference to the result
     * @access Private
     */
    function __afterExecution(&$result) {
        if ($this->__connection_keep_open) {
            return;
        }
        $this->close(); 
    }
    
    /**
     * Gets the result of execution as an array or one option. Read more
     * "http://www.php.net/manual/en/function.curl-getinfo.php"
     * 
     * @param String $opt - Name of option in result
     * @access Public
     * @return Array of result indicators, or one option
     */
    function getExecutionInfo($opt = false) {
        $this->debug_log("Retrieving execution info by option [$opt]");
        return ($opt) ? $this->__connection_execution_info[$opt] : $this->__connection_execution_info;
    }
    
    /**
     * Closes opened CUrl connection
     * Notice: if you are using open connection do not forget to close it
     * 
     * @access Public
     */
    function close() {
        if (empty($this->__connection)) {
            return;
        }
        $this->debug_log("Closing CURL connection");
        curl_close($this->__connection);
        unset($this->__connection);
    }
    
    /**
     * Uploads a file on usualy FTP location $url
     * 
     * @param String $url - location to upload file
     * @param Array $options - possible values:
     *         file - a path of the file to upload; Mandatory
     *         username - username used then logging in; Default - anonymous
     *         username - password used then logging in; Default - anonymous
     * @access Public
     * @return Boolean - true on success, false on failure
     */
    function uploadRemoteFile($url, $options) {
        $this->debug_log("Uploading Remote File to location [$url]");
        $this->__beforeExecution($url);
        
        if (!array_key_exists('file', $options)) {
            $this->debug_log("FAILED to upload file, no file was specified in options", RCP_DEBUG_LEVEL_ERRORS);
        }
        
        $result = false;
        if (file_exists($options['file']) && (($file = fopen($options['file'], "r")) !== false)) {
            //set additional curl options
            curl_setopt($this->__connection, CURLOPT_INFILE, $file);
             curl_setopt($this->__connection, CURLOPT_INFILESIZE, filesize($options['file']));
             curl_setopt($this->__connection, CURLOPT_UPLOAD, true);
             $credentials = empty($options['username']) ? 'anonymous' : $options['username'];
             $credentials .= ':';
             $credentials .= empty($options['password']) ? 'anonymous' : $options['password'];
            curl_setopt($this->__connection, CURLOPT_USERPWD, $credentials);
            
            curl_exec($this->__connection);
            $this->__connection_execution_info = curl_getinfo($this->__connection);
            curl_close($this->__connection);
            fclose($file);
            
            $kbs = sprintf("%01.2f KB", $this->getExecutionInfo('size_upload') / 1024);
            $this->debug_log("Uploading file[$kbs], HTTP_CODE [".$this->getExecutionInfo('http_code')."]");
            $result = $this->getExecutionInfo('http_code') == 200 || $this->getExecutionInfo('http_code') == 301;
        }
        else {
            $this->debug_log("FAILED to upload file, could not open [{$options['file']}]", RCP_DEBUG_LEVEL_ERRORS);
        }
        $this->__afterExecution($result);
        return $result;
    }
    
    /**
     * Downloads file from remote location
     * 
     * @param String $url - URL of file to download
     * @param String $location - path of the file to save downloaded
     * @access Public
     * @return Boolean - true on success, false on failure
     */
    function downloadRemoteFile($url, $location) {
        $this->debug_log("Downloading Remote File from location [$url]");
        $this->__beforeExecution($url);
        
        curl_setopt($this->__connection, CURLOPT_HEADER, 0);
        curl_setopt($this->__connection, CURLOPT_ENCODING, ''); //accepts all types of encoded content
        curl_setopt($this->__connection, CURLOPT_RETURNTRANSFER, 0);
        curl_setopt($this->__connection, CURLOPT_URL, $url);
        
        $result = false;
        if (($file = fopen($location, "w")) !== false) {
            //set additional curl options
            curl_setopt($this->__connection, CURLOPT_FILE, $file);
            curl_exec($this->__connection);
            $this->__connection_execution_info = curl_getinfo($this->__connection);
            fclose($file);
            
            $kbs = sprintf("%01.2f KB", $this->getExecutionInfo('size_download') / 1024);
            $this->debug_log("Downloading file [$kbs], HTTP_CODE [".$this->getExecutionInfo('http_code')."]");
            $result = $this->getExecutionInfo('http_code') == 200 || $this->getExecutionInfo('http_code') == 301;
        }
        else {
            $this->debug_log("FAILED to open file [$location] for writing", RCP_DEBUG_LEVEL_ERRORS);
        }
        
        $this->__afterExecution($result);
        return $result;
    }
    
    /**
     * Downloads remote content using POST data
     * 
     * @param String $url - URL of the page to download
     * @param Mixed $post - POST data can be passed as:
     *         Array - pairs of key/values (e.g.: array('username' => 'gosu', 'password' => 'hard_to_guess')
     *         String - sometimes POST data must be sent as string (e.g.: username=gosu&password=hard_to_guess)
     * @param String $referer - link of which we are referring to this $url
     * @access Public
     * @return Boolean - true on success, false on failure
     */
    function openPostRemoteFile($url, $post = array(), $referer = '') {
        $this->debug_log("Opening Remote File with Post data on location [$url]");
        $this->__beforeExecution($url);
        
        curl_setopt($this->__connection, CURLOPT_SSL_VERIFYPEER, true);
        if (ereg('^(https)', $url)) {
            curl_setopt($this->__connection, CURLOPT_SSL_VERIFYPEER, false);
        }
        
        curl_setopt($this->__connection, CURLOPT_REFERER, $referer);
        curl_setopt($this->__connection, CURLOPT_POST, 1);
        curl_setopt($this->__connection, CURLOPT_ENCODING, ''); //accepts all types of encoded content
        curl_setopt($this->__connection, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($this->__connection, CURLOPT_POSTFIELDS, $post);
        curl_setopt($this->__connection, CURLOPT_URL, $url);
        
        $content = curl_exec($this->__connection);
        $this->setContent($content);
        $this->__connection_execution_info = curl_getinfo($this->__connection);
        
        $kbs = sprintf("%01.2f KB", $this->getExecutionInfo('size_download') / 1024);
        $this->debug_log("Got remote content size [$kbs], HTTP_CODE [".$this->getExecutionInfo('http_code')."]");
        
        $result = $this->getExecutionInfo('http_code') == 200 || $this->getExecutionInfo('http_code') == 301;
        $this->__afterExecution($result);
        return $result;
    }
    
    /**
     * Downloads content from web site
     * 
     * @param String $url - URL of the page to download
     * @param String $referer - link of which we are referring to this $url
     * @access Public
     * @return Boolean - true on success, false on failure
     */
    function openRemoteFile($url, $referer = '') {
        $this->debug_log("Opening Remote File on location [$url]");
        $this->__beforeExecution($url);
        
        curl_setopt($this->__connection, CURLOPT_SSL_VERIFYPEER, true);
        if (ereg('^(https)', $url)) {
            curl_setopt($this->__connection, CURLOPT_SSL_VERIFYPEER, false);
        }
        
        curl_setopt($this->__connection, CURLOPT_REFERER, $referer);
        curl_setopt($this->__connection, CURLOPT_POST, 0);
        curl_setopt($this->__connection, CURLOPT_ENCODING, ''); //accepts all types of encoded content
        curl_setopt($this->__connection, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($this->__connection, CURLOPT_URL, $url);
        
        $content = curl_exec($this->__connection);
        $this->setContent($content);
        $this->__connection_execution_info = curl_getinfo($this->__connection);
        
        $kbs = sprintf("%01.2f KB", $this->getExecutionInfo('size_download') / 1024);
        $this->debug_log("Got remote content size [$kbs], HTTP_CODE [".$this->getExecutionInfo('http_code')."]");
        
        $result = $this->getExecutionInfo('http_code') == 200 || $this->getExecutionInfo('http_code') == 301;
        $this->__afterExecution($result);
        return $result;
    }
    
    /**
     * Sets the content
     * 
     * @param String $content - content
     * @access Public
     */
    function setContent($content) {
        unset($this->__content);
        $this->__content = $content;
        $this->offset = 0;
        $this->length = strlen($this->__content);
        $this->debug_log("setting new content, length [$this->length]");
    }
    
    /**
     * Gets the reference to content
     * 
     * @access Public
     * @return String - reference to content
     */
    function &getContent() {
        return $this->__content;
    }
    
    /**
     * Reads content from offset till the first occurrence of given $pattern
     * and sets the offset at the ending of the pattern
     * 
     * @param String $pattern - pattern to search for
     * @access Public
     * @return String - content till given pattern on success
     *         boolean false on failure, must be checked like (e.g.: $result === false)
     */
    function readTo($pattern) {
        $result = false;
        if (empty($this->__content)) {
            return $result;
        }
        
        $this->debug_log("Reading content till PATTERN[$pattern], OFFSET[$this->offset]");
        $posTo = strpos($this->__content, $pattern, $this->offset);
        if ($posTo !== false) {
            $length = $posTo - $this->offset;
            $result = substr($this->__content, $this->offset, $length);
            $this->offset = $posTo + strlen($pattern); 
        }
        return $result;
    }
    
    /**
     * Reads block of content delimited by $from and $to patterns
     * 
     * @param String $from - begining of block
     * @param String $to - ending of block
     * @access Public
     * @return String - content block on success
     *         boolean false on failure, must be checked like (e.g.: $result === false)
     */
    function readFromTo($from, $to) {
        $result = false;
        if (empty($this->__content)) {
            return $result;
        }
        
        $this->debug_log("Reading content FROM PATTERN[$from] TO PATTERN [$to], OFFSET[$this->offset]");
        $posFrom = strpos($this->__content, $from, $this->offset);
        if ($posFrom !== false) {
            $posFrom += strlen($from);
            $posTo = strpos($this->__content, $to, $posFrom);
            if ($posTo !== false) {
                $length = $posTo - $posFrom;
                $result = substr($this->__content, $posFrom, $length);
                $this->offset = $posTo + strlen($to);
            }
        }
        return $result;
    }
}
?> 